package com.fourth.util;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;

/**
 * @author Mr.ma
 * @since 2023/3/8 20:06
 **/
@Component
public class RedisLockUtil {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 加锁操作
     * @param key 一般为id或其它唯一标识
     * @param value 时间戳
     * @return
     */
    public boolean lock(String key, String value){
        // 使用setIfAbsent（相当于setnx）存储key和value
        if (stringRedisTemplate.opsForValue().setIfAbsent(key, value)){
            // 保存成功，则说明该线程获取到了锁，返回true
            return true;
        }

        // 没有成功保存，则说明锁被其他线程占有
        // 获取占有锁的线程的时间戳
        String currentValue = stringRedisTemplate.opsForValue().get(key);
        // 如果系统当前时间大于占有锁的线程分配到的时间戳时，则说明占有锁的线程运行超时，
        // 将锁重新分配给第一个进入的线程
        if(!StringUtils.isEmpty(currentValue) && Long.valueOf(currentValue) < System.currentTimeMillis()){
            // 获取到当前占有锁的线程的时间戳，
            // 并将当前进入线程的时间戳设置进去，用于进一步校验
            String oldValue = stringRedisTemplate.opsForValue().getAndSet(key, value);
            // getAndSet()方法一次提交一条修改
            // 当线程获取到的oldValue与之前获取到的currentValue相同时，说明该线程为第一个进入的线程
            // 避免两个线程同时进入
            if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)){
                return true;
            }
        }
        return false;
    }

    /**
     * 释放锁
     * @param key
     * @param value
     */
    public void unLock(String key, String value){
        String currentValue = stringRedisTemplate.opsForValue().get(key);

        if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)){
            stringRedisTemplate.opsForValue().getOperations().delete(key);
        }
    }
}
